/*-------------------------------------------------------------------------
 *
 * heapfuncs.c
 *	  调查 heap 页的函数
 *
 * 我们检查这些函数的输入，以防出现可能导致崩溃的损坏指针等问题，但同时我们尝试尽可能多地打印出信息，即使这些信息是无意义的。因为如果页面损坏，我们不知道它是为什么以及如何损坏的，所以让用户自行判断。
 *
 * 这些函数对超级用户是受限的，因为如果输入检查不够严格，可能会引入安全漏洞。
 * 您需要是超级用户才能获取原始页面图像，因此几乎没有使用这些函数而不具备超级用户权限的用例。
 *
 * Copyright (c) 2007-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  contrib/pageinspect/heapfuncs.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/htup_details.h"
#include "access/relation.h"
#include "catalog/pg_am_d.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "pageinspect.h"
#include "port/pg_bitutils.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/rel.h"

/*
 * 不再支持创建带有 oids 的元组，但在使用 pg_upgrade 从旧版本升级时，元组仍可能具有 oid。似乎显示这一点是值得的。
 */
#define HeapTupleHeaderGetOidOld(tup) \
( \
	((tup)->t_infomask & HEAP_HASOID_OLD) ? \
	   *((Oid *) ((char *)(tup) + (tup)->t_hoff - sizeof(Oid))) \
	: \
		InvalidOid \
)


/*
 * bits_to_text
 *
 * 将 'len' 位的 bits8 数组转换为人类可读的 c-string 表示。
 */
static char * fc_bits_to_text(bits8 *fc_bits, int fc_len)
{
	int			fc_i;
	char	   *fc_str;

	fc_str = palloc(fc_len + 1);

	for (fc_i = 0; fc_i < fc_len; fc_i++)
		fc_str[fc_i] = (fc_bits[(fc_i / 8)] & (1 << (fc_i % 8))) ? '1' : '0';

	fc_str[fc_i] = '\0';

	return fc_str;
}


/*
 * text_to_bits
 *
 * 将位的 c-string 表示转换为 bits8 数组。这是之前例程的逆操作。
 */
static bits8 *
fc_text_to_bits(char *fc_str, int fc_len)
{
	bits8	   *fc_bits;
	int			fc_off = 0;
	char		fc_byte = 0;

	fc_bits = palloc(fc_len + 1);

	while (fc_off < fc_len)
	{
		if (fc_off % 8 == 0)
			fc_byte = 0;

		if ((fc_str[fc_off] == '0') || (fc_str[fc_off] == '1'))
			fc_byte = fc_byte | ((fc_str[fc_off] - '0') << fc_off % 8);
		else
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("invalid character \"%.*s\" in t_bits string",
							pg_mblen(fc_str + fc_off), fc_str + fc_off)));

		if (fc_off % 8 == 7)
			fc_bits[fc_off / 8] = fc_byte;

		fc_off++;
	}

	return fc_bits;
}

/*
 * heap_page_items
 *
 * 允许检查堆页面的行指针和元组头部。
 */
PG_FUNCTION_INFO_V1(heap_page_items);

typedef struct heap_page_items_state
{
	TupleDesc	tupd;
	Page		page;
	uint16		offset;
} heap_page_items_state;

Datum heap_page_items(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	heap_page_items_state *fc_inter_call_data = NULL;
	FuncCallContext *fc_fctx;
	int			fc_raw_page_size;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	fc_raw_page_size = VARSIZE(fc_raw_page) - VARHDRSZ;

	if (SRF_IS_FIRSTCALL())
	{
		TupleDesc	fc_tupdesc;
		MemoryContext fc_mctx;

		if (fc_raw_page_size < SizeOfPageHeaderData)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("input page too small (%d bytes)", fc_raw_page_size)));

		fc_fctx = SRF_FIRSTCALL_INIT();
		fc_mctx = MemoryContextSwitchTo(fc_fctx->multi_call_memory_ctx);

		fc_inter_call_data = palloc(sizeof(heap_page_items_state));

		/* 为我们的结果类型构建一个元组描述符 */
		if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
			elog(ERROR, "return type must be a row type");

		fc_inter_call_data->tupd = fc_tupdesc;

		fc_inter_call_data->offset = FirstOffsetNumber;
		fc_inter_call_data->page = VARDATA(fc_raw_page);

		fc_fctx->max_calls = PageGetMaxOffsetNumber(fc_inter_call_data->page);
		fc_fctx->user_fctx = fc_inter_call_data;

		MemoryContextSwitchTo(fc_mctx);
	}

	fc_fctx = SRF_PERCALL_SETUP();
	fc_inter_call_data = fc_fctx->user_fctx;

	if (fc_fctx->call_cntr < fc_fctx->max_calls)
	{
		Page		fc_page = fc_inter_call_data->page;
		HeapTuple	fc_resultTuple;
		Datum		fc_result;
		ItemId		fc_id;
		Datum		fc_values[14];
		bool		fc_nulls[14];
		uint16		fc_lp_offset;
		uint16		fc_lp_flags;
		uint16		fc_lp_len;

		memset(fc_nulls, 0, sizeof(fc_nulls));

		/* 从行指针中提取信息 */

		fc_id = PageGetItemId(fc_page, fc_inter_call_data->offset);

		fc_lp_offset = ItemIdGetOffset(fc_id);
		fc_lp_flags = ItemIdGetFlags(fc_id);
		fc_lp_len = ItemIdGetLength(fc_id);

		fc_values[0] = UInt16GetDatum(fc_inter_call_data->offset);
		fc_values[1] = UInt16GetDatum(fc_lp_offset);
		fc_values[2] = UInt16GetDatum(fc_lp_flags);
		fc_values[3] = UInt16GetDatum(fc_lp_len);

		/*
		 * 我们只进行足够的有效性检查，以确保我们不会引用
		 * 超出传递给我们的页面的数据。页面可能以其他许多方式损坏，
		 * 但至少我们不会崩溃。
		 */
		if (ItemIdHasStorage(fc_id) &&
			fc_lp_len >= MinHeapTupleSize &&
			fc_lp_offset == MAXALIGN(fc_lp_offset) &&
			fc_lp_offset + fc_lp_len <= fc_raw_page_size)
		{
			HeapTupleHeader fc_tuphdr;
			bytea	   *fc_tuple_data_bytea;
			int			fc_tuple_data_len;

			/* 从元组头部提取信息 */

			fc_tuphdr = (HeapTupleHeader) PageGetItem(fc_page, fc_id);

			fc_values[4] = UInt32GetDatum(HeapTupleHeaderGetRawXmin(fc_tuphdr));
			fc_values[5] = UInt32GetDatum(HeapTupleHeaderGetRawXmax(fc_tuphdr));
			/* 与 xvac 共享 */
			fc_values[6] = UInt32GetDatum(HeapTupleHeaderGetRawCommandId(fc_tuphdr));
			fc_values[7] = PointerGetDatum(&fc_tuphdr->t_ctid);
			fc_values[8] = UInt32GetDatum(fc_tuphdr->t_infomask2);
			fc_values[9] = UInt32GetDatum(fc_tuphdr->t_infomask);
			fc_values[10] = UInt8GetDatum(fc_tuphdr->t_hoff);

			/* 将原始元组数据复制到 bytea 属性中 */
			fc_tuple_data_len = fc_lp_len - fc_tuphdr->t_hoff;
			fc_tuple_data_bytea = (bytea *) palloc(fc_tuple_data_len + VARHDRSZ);
			SET_VARSIZE(fc_tuple_data_bytea, fc_tuple_data_len + VARHDRSZ);
			memcpy(VARDATA(fc_tuple_data_bytea), (char *) fc_tuphdr + fc_tuphdr->t_hoff,
				   fc_tuple_data_len);
			fc_values[13] = PointerGetDatum(fc_tuple_data_bytea);

			/*
			 * 我们已经检查了该项完全位于传递给我们的原始
			 * 页面内，行指针中给出的长度也是如此。
			 * 在使用 t_bits 和 oid 进行访问之前，让我们检查 t_hoff 是否
			 * 不会超出 lp_len。
			 */
			if (fc_tuphdr->t_hoff >= SizeofHeapTupleHeader &&
				fc_tuphdr->t_hoff <= fc_lp_len &&
				fc_tuphdr->t_hoff == MAXALIGN(fc_tuphdr->t_hoff))
			{
				if (fc_tuphdr->t_infomask & HEAP_HASNULL)
				{
					int			fc_bits_len;

					fc_bits_len =
						BITMAPLEN(HeapTupleHeaderGetNatts(fc_tuphdr)) * BITS_PER_BYTE;
					fc_values[11] = CStringGetTextDatum(fc_bits_to_text(fc_tuphdr->t_bits, fc_bits_len));
				}
				else
					fc_nulls[11] = true;

				if (fc_tuphdr->t_infomask & HEAP_HASOID_OLD)
					fc_values[12] = HeapTupleHeaderGetOidOld(fc_tuphdr);
				else
					fc_nulls[12] = true;
			}
			else
			{
				fc_nulls[11] = true;
				fc_nulls[12] = true;
			}
		}
		else
		{
			/*
			 * 行指针未使用，或无效。将其余字段设置为 NULL。
			 */
			int			fc_i;

			for (fc_i = 4; fc_i <= 13; fc_i++)
				fc_nulls[fc_i] = true;
		}

		/* 构建并返回结果元组。 */
		fc_resultTuple = heap_form_tuple(fc_inter_call_data->tupd, fc_values, fc_nulls);
		fc_result = HeapTupleGetDatum(fc_resultTuple);

		fc_inter_call_data->offset++;

		SRF_RETURN_NEXT(fc_fctx, fc_result);
	}
	else
		SRF_RETURN_DONE(fc_fctx);
}

/*
 * tuple_data_split_internal
 *
 * 将直接从页面提取的原始元组数据分割成 bytea 元素的数组。
 * 此例程对 NULL 值进行查找并相应地创建数组元素。这是对
 * heaptuple.c 中 nocachegetattr() 的重新实现，简化为教学目的。
 */
static Datum fc_tuple_data_split_internal(Oid fc_relid, char *fc_tupdata,
						  uint16 fc_tupdata_len, uint16 fc_t_infomask,
						  uint16 fc_t_infomask2, bits8 *fc_t_bits,
						  bool fc_do_detoast)
{
	ArrayBuildState *fc_raw_attrs;
	int			fc_nattrs;
	int			fc_i;
	int			fc_off = 0;
	Relation	fc_rel;
	TupleDesc	fc_tupdesc;

	/* 从关系 OID 获取元组描述符 */
	fc_rel = relation_open(fc_relid, AccessShareLock);
	fc_tupdesc = RelationGetDescr(fc_rel);

	fc_raw_attrs = initArrayResult(BYTEAOID, CurrentMemoryContext, false);
	fc_nattrs = fc_tupdesc->natts;

	/*
	 * 序列始终使用堆 AM，但它们在目录中并未显示这一点。
	 */
	if (fc_rel->rd_rel->relkind != RELKIND_SEQUENCE &&
		fc_rel->rd_rel->relam != HEAP_TABLE_AM_OID)
		ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						errmsg("only heap AM is supported")));

	if (fc_nattrs < (fc_t_infomask2 & HEAP_NATTS_MASK))
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("number of attributes in tuple header is greater than number of attributes in tuple descriptor")));

	for (fc_i = 0; fc_i < fc_nattrs; fc_i++)
	{
		Form_pg_attribute fc_attr;
		bool		fc_is_null;
		bytea	   *fc_attr_data = NULL;

		fc_attr = TupleDescAttr(fc_tupdesc, fc_i);

		/*
		 * 元组头部可以指定比元组描述符更少的属性，因为
		 * ALTER TABLE ADD COLUMN 不带 DEFAULT 关键字实际上
		 * 并不会改变页面中的元组，因此属性编号大于
		 * (t_infomask2 & HEAP_NATTS_MASK) 的应视为 NULL。
		 */
		if (fc_i >= (fc_t_infomask2 & HEAP_NATTS_MASK))
			fc_is_null = true;
		else
			fc_is_null = (fc_t_infomask & HEAP_HASNULL) && att_isnull(fc_i, fc_t_bits);

		if (!fc_is_null)
		{
			int			fc_len;

			if (fc_attr->attlen == -1)
			{
				fc_off = att_align_pointer(fc_off, fc_attr->attalign, -1,
										fc_tupdata + fc_off);

				/*
				 * 由于 VARSIZE_ANY 如果无法正确
				 * 检测 VARTAG_SIZE 宏中的外部存储类型则会抛出异常，
				 * 因此此检查被重复以实现更好的错误处理。
				 */
				if (VARATT_IS_EXTERNAL(fc_tupdata + fc_off) &&
					!VARATT_IS_EXTERNAL_ONDISK(fc_tupdata + fc_off) &&
					!VARATT_IS_EXTERNAL_INDIRECT(fc_tupdata + fc_off))
					ereport(ERROR,
							(errcode(ERRCODE_DATA_CORRUPTED),
							 errmsg("first byte of varlena attribute is incorrect for attribute %d", fc_i)));

				fc_len = VARSIZE_ANY(fc_tupdata + fc_off);
			}
			else
			{
				fc_off = att_align_nominal(fc_off, fc_attr->attalign);
				fc_len = fc_attr->attlen;
			}

			if (fc_tupdata_len < fc_off + fc_len)
				ereport(ERROR,
						(errcode(ERRCODE_DATA_CORRUPTED),
						 errmsg("unexpected end of tuple data")));

			if (fc_attr->attlen == -1 && fc_do_detoast)
				fc_attr_data = DatumGetByteaPCopy(fc_tupdata + fc_off);
			else
			{
				fc_attr_data = (bytea *) palloc(fc_len + VARHDRSZ);
				SET_VARSIZE(fc_attr_data, fc_len + VARHDRSZ);
				memcpy(VARDATA(fc_attr_data), fc_tupdata + fc_off, fc_len);
			}

			fc_off = att_addlength_pointer(fc_off, fc_attr->attlen,
										fc_tupdata + fc_off);
		}

		fc_raw_attrs = accumArrayResult(fc_raw_attrs, PointerGetDatum(fc_attr_data),
									 fc_is_null, BYTEAOID, CurrentMemoryContext);
		if (fc_attr_data)
			pfree(fc_attr_data);
	}

	if (fc_tupdata_len != fc_off)
		ereport(ERROR,
				(errcode(ERRCODE_DATA_CORRUPTED),
				 errmsg("end of tuple reached without looking at all its data")));

	relation_close(fc_rel, AccessShareLock);

	return makeArrayResult(fc_raw_attrs, CurrentMemoryContext);
}

/*
 * tuple_data_split
 *
 * 将直接从页面提取的原始元组数据分割成不同的元素
 * 考虑到 NULL 值。
 */
PG_FUNCTION_INFO_V1(tuple_data_split);

Datum tuple_data_split(PG_FUNCTION_ARGS)
{
	Oid			fc_relid;
	bytea	   *fc_raw_data;
	uint16		fc_t_infomask;
	uint16		fc_t_infomask2;
	char	   *fc_t_bits_str;
	bool		fc_do_detoast = false;
	bits8	   *fc_t_bits = NULL;
	Datum		fc_res;

	fc_relid = PG_GETARG_OID(0);
	fc_raw_data = PG_ARGISNULL(1) ? NULL : PG_GETARG_BYTEA_P(1);
	fc_t_infomask = PG_GETARG_INT16(2);
	fc_t_infomask2 = PG_GETARG_INT16(3);
	fc_t_bits_str = PG_ARGISNULL(4) ? NULL :
		text_to_cstring(PG_GETARG_TEXT_PP(4));

	if (PG_NARGS() >= 6)
		fc_do_detoast = PG_GETARG_BOOL(5);

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	if (!fc_raw_data)
		PG_RETURN_NULL();

	/*
	 * 将 t_bits 字符串转换回元组头部表示的 bits8 数组。
	 */
	if (fc_t_infomask & HEAP_HASNULL)
	{
		size_t		fc_bits_str_len;
		size_t		fc_bits_len;

		fc_bits_len = BITMAPLEN(fc_t_infomask2 & HEAP_NATTS_MASK) * BITS_PER_BYTE;
		if (!fc_t_bits_str)
#ifdef FDD //cppcheck
		{
#endif
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("t_bits string must not be NULL")));
#ifdef FDD //cppcheck
			PG_RETURN_VOID();
		}
#endif
		fc_bits_str_len = strlen(fc_t_bits_str);
		if (fc_bits_len != fc_bits_str_len)
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("unexpected length of t_bits string: %zu, expected %zu",
							fc_bits_str_len, fc_bits_len)));

		/* 进行转换 */
		fc_t_bits = fc_text_to_bits(fc_t_bits_str, fc_bits_str_len);
	}
	else
	{
		if (fc_t_bits_str)
			ereport(ERROR,
					(errcode(ERRCODE_DATA_CORRUPTED),
					 errmsg("t_bits string is expected to be NULL, but instead it is %zu bytes long",
							strlen(fc_t_bits_str))));
	}

	/* 分割元组数据 */
	fc_res = fc_tuple_data_split_internal(fc_relid, (char *) fc_raw_data + VARHDRSZ,
									VARSIZE(fc_raw_data) - VARHDRSZ,
									fc_t_infomask, fc_t_infomask2, fc_t_bits,
									fc_do_detoast);

	if (fc_t_bits)
		pfree(fc_t_bits);

	PG_RETURN_ARRAYTYPE_P(fc_res);
}

/*
 * heap_tuple_infomask_flags
 *
 * 解码为人类可读的格式 t_infomask 和 t_infomask2，
 * 这些与元组相关联。所有标志在 access/htup_details.h 中进行了描述。
 */
PG_FUNCTION_INFO_V1(heap_tuple_infomask_flags);

Datum heap_tuple_infomask_flags(PG_FUNCTION_ARGS)
{
#define HEAP_TUPLE_INFOMASK_COLS 2
	Datum		fc_values[HEAP_TUPLE_INFOMASK_COLS];
	bool		fc_nulls[HEAP_TUPLE_INFOMASK_COLS];
	uint16		fc_t_infomask = PG_GETARG_INT16(0);
	uint16		fc_t_infomask2 = PG_GETARG_INT16(1);
	int			fc_cnt = 0;
	ArrayType  *fc_a;
	int			fc_bitcnt;
	Datum	   *fc_flags;
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_tuple;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	/* 为我们的结果类型构建一个元组描述符 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	fc_bitcnt = pg_popcount((const char *) &fc_t_infomask, sizeof(uint16)) +
		pg_popcount((const char *) &fc_t_infomask2, sizeof(uint16));

	/* 初始化值和 NULL 标志数组 */
	MemSet(fc_values, 0, sizeof(fc_values));
	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	/* 如果没有标志，返回一组空数组 */
	if (fc_bitcnt <= 0)
	{
		fc_values[0] = PointerGetDatum(construct_empty_array(TEXTOID));
		fc_values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
		fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
		PG_RETURN_DATUM(HeapTupleGetDatum(fc_tuple));
	}

	/* 构建原始标志集 */
	fc_flags = (Datum *) palloc0(sizeof(Datum) * fc_bitcnt);

	/* 解码 t_infomask */
	if ((fc_t_infomask & HEAP_HASNULL) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_HASNULL");
	if ((fc_t_infomask & HEAP_HASVARWIDTH) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_HASVARWIDTH");
	if ((fc_t_infomask & HEAP_HASEXTERNAL) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_HASEXTERNAL");
	if ((fc_t_infomask & HEAP_HASOID_OLD) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_HASOID_OLD");
	if ((fc_t_infomask & HEAP_XMAX_KEYSHR_LOCK) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_KEYSHR_LOCK");
	if ((fc_t_infomask & HEAP_COMBOCID) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_COMBOCID");
	if ((fc_t_infomask & HEAP_XMAX_EXCL_LOCK) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_EXCL_LOCK");
	if ((fc_t_infomask & HEAP_XMAX_LOCK_ONLY) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_LOCK_ONLY");
	if ((fc_t_infomask & HEAP_XMIN_COMMITTED) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMIN_COMMITTED");
	if ((fc_t_infomask & HEAP_XMIN_INVALID) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMIN_INVALID");
	if ((fc_t_infomask & HEAP_XMAX_COMMITTED) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_COMMITTED");
	if ((fc_t_infomask & HEAP_XMAX_INVALID) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_INVALID");
	if ((fc_t_infomask & HEAP_XMAX_IS_MULTI) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_IS_MULTI");
	if ((fc_t_infomask & HEAP_UPDATED) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_UPDATED");
	if ((fc_t_infomask & HEAP_MOVED_OFF) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_MOVED_OFF");
	if ((fc_t_infomask & HEAP_MOVED_IN) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_MOVED_IN");

	/* 解码 t_infomask2 */
	if ((fc_t_infomask2 & HEAP_KEYS_UPDATED) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_KEYS_UPDATED");
	if ((fc_t_infomask2 & HEAP_HOT_UPDATED) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_HOT_UPDATED");
	if ((fc_t_infomask2 & HEAP_ONLY_TUPLE) != 0)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_ONLY_TUPLE");

	/* 构建值 */
	Assert(fc_cnt <= fc_bitcnt);
	fc_a = construct_array(fc_flags, fc_cnt, TEXTOID, -1, false, TYPALIGN_INT);
	fc_values[0] = PointerGetDatum(fc_a);

	/*
	 * 构建组合标志集。使用与之前相同的数组，这
	 * 保持代码简单。
	 */
	fc_cnt = 0;
	MemSet(fc_flags, 0, sizeof(Datum) * fc_bitcnt);

	/* 解码 t_infomask 的组合掩码 */
	if ((fc_t_infomask & HEAP_XMAX_SHR_LOCK) == HEAP_XMAX_SHR_LOCK)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMAX_SHR_LOCK");
	if ((fc_t_infomask & HEAP_XMIN_FROZEN) == HEAP_XMIN_FROZEN)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_XMIN_FROZEN");
	if ((fc_t_infomask & HEAP_MOVED) == HEAP_MOVED)
		fc_flags[fc_cnt++] = CStringGetTextDatum("HEAP_MOVED");

	/* 如果没有组合标志，则构建一个空数组 */
	if (fc_cnt == 0)
		fc_a = construct_empty_array(TEXTOID);
	else
		fc_a = construct_array(fc_flags, fc_cnt, TEXTOID, -1, false, TYPALIGN_INT);
	pfree(fc_flags);
	fc_values[1] = PointerGetDatum(fc_a);

	/* 将记录作为 Datum 返回 */
	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);
	PG_RETURN_DATUM(HeapTupleGetDatum(fc_tuple));
}
